import post from '@/client/utils/request/post';
import { ElMessage } from 'element-plus';

interface FileChunk {
  chunk: Blob;
  hash: string;
  percentage: number;
  index: number;
  fileHash: string;
}

let worker: Worker;

export default function useUpload() {
  const file = ref<File | null>(null);
  const fileChunks = ref<Array<FileChunk>>([]);
  const hash = ref<string>('');
  const hashPercentage = ref<number>(0);
  const requestList = ref<XMLHttpRequest[]>([]);
  const isPaused = ref<boolean>(false);

  function handleFileChange(e: Event) {
    const { files } = e.target as HTMLInputElement;
    if (!files) {
      return;
    }

    file.value = files[0];
  }

  async function handleUploadFile() {
    if (!file.value) {
      return;
    }

    // 1.把上传的文件切片
    const fileChunkList = createFileChunks(file.value, FILE_SIZE);

    // 2.根据文件内容生成 hash
    hash.value = await genFileHash(fileChunkList);

    // 3. 在上传前，先计算出文件 hash，并把 hash 发送给服务端进行验证，由于 hash 的唯一性，所以一旦服务端能找到 hash 相同的文件，则直接返回上传成功的信息即可
    const { shouldUpload, msg, uploadedList } = await verifyUploaded(
      file.value.name,
      hash.value,
    );
    if (!shouldUpload) {
      ElMessage.success(msg);
      return;
    }

    // 4. 将文件切片信息存储到 fileChunks 中
    fileChunks.value = fileChunkList.map(({ chunk }, index) => ({
      fileHash: `${hash.value}`,
      chunk,
      hash: `${file.value?.name}-${index}`,
      percentage: 0,
      index,
    }));

    // 5. 上传文件, 同时过滤已上传的切片
    await uploadFileChunks(uploadedList);
  }

  function handlePause() {
    requestList.value.forEach((xhr) => xhr.abort());
    requestList.value = [];
    isPaused.value = true;
  }

  async function handleResume() {
    const { uploadedList } = await verifyUploaded(file.value!.name, hash.value);

    await uploadFileChunks(uploadedList);
  }

  return {
    handleFileChange,
    handleUploadFile,
    fileChunks,
    file,
    hash,
    hashPercentage,
    handlePause,
    isPaused,
    handleResume,
  };

  //   切割文件
  function createFileChunks(file: Blob, size: number) {
    const chunks: Array<{ chunk: Blob }> = [];
    for (let i = 0; i < file.size; i += size) {
      chunks.push({
        chunk: file.slice(i, i + size),
      });
    }

    return chunks;
  }

  async function uploadFileChunks(uploadedList: string[] = []) {
    const requests = fileChunks.value
      .filter(
        ({ fileHash, hash }) =>
          !uploadedList.includes(`${fileHash}-${hash.split('-')[1]}`),
      )
      .map(({ chunk, hash, fileHash, index }) => {
        const formData = new FormData();
        formData.append('file', chunk);
        formData.append('hash', hash);
        formData.append('fileHash', fileHash);
        formData.append('filename', file.value?.name || 'filename');

        return { formData, index };
      })
      .map(async ({ formData, index }) =>
        post('/api/upload', {
          data: formData,
          onProgress: (e: any) => {
            fileChunks.value[index].percentage = Math.round(
              (e.loaded / e.total) * 100,
            );
          },
          requestList: requestList.value,
        }),
      );

    await Promise.all(requests);

    // 6. 合并文件
    // 之前上传的切片数量 + 本次上传的切片数量 = 所有切片数量时合并切片
    if (uploadedList.length + requests.length === fileChunks.value.length) {
      await mergeFileChunks();
    }
  }

  async function mergeFileChunks() {
    return post('/api/merge', {
      data: JSON.stringify({
        filename: file.value?.name,
        fileHash: hash.value,
        ext: file.value?.name?.split('.')[1],
      }),
      headers: { 'content-type': 'application/json' },
    });
  }

  function genFileHash(fileChunkList: { chunk: Blob }[]): Promise<string> {
    return new Promise((resolve) => {
      // 添加 worker 属性
      worker = new Worker('./hash.js');
      worker.postMessage({ fileChunks: fileChunkList });
      worker.onmessage = (e: any) => {
        const { percentage, hash } = e.data;
        hashPercentage.value = percentage;

        if (hash) {
          resolve(hash);
        }
      };
    });
  }

  async function verifyUploaded(
    filename: string,
    fileHash: string,
  ): Promise<any> {
    const data: any = await post('/api/verify', {
      data: JSON.stringify({
        filename,
        fileHash,
      }),
      headers: { 'content-type': 'application/json' },
    });

    return JSON.parse(data);
  }
}
